<!doctype html>
<html>
  <head>
    <title>Test multichannel support.</title>
    <style type="text/css">
      body {
        margin: 2em;
      }
      .manual-test-ui {
        font-family: Arial;
        padding: 1em;
        border: 1px solid #999;
      }
      .manual-test-ui button {
        padding: 1em;
        font-size: 1em;
      }
    </style>
  </head>

  <body>
    <h1>Test Multichannel Audio Output</h1>

    <p>Tests that multichannel audio output (> 8 channels) is working correctly.
      This test cannot be run with an offline audio context because it requires
      an actual audio hardware with the multichannel capability.</p>

    <p>Press "Start Test Tone" to run the test. You should hear an one-second
      sine tone from all the available audio output channels from the channel 1
      to the last channel.</p>

    <p>Note that this test only works on OSX because CoreAudio driver supports
      the multichannel streams (more than 2) on a single audio device whereas
      other platforms do not.</p>

    <p>CRBUG issue: <a href="https://code.google.com/p/chromium/issues/detail?id=424795" target="_blank">
      424795</a></p>

    <div class="manual-test-ui">
      <p>Max Channel Count: <span id="eMaxChannelCount">2</span></p>
      <p>Currently playing: <span id="eChannelIndex">NONE</span></p>
      <button id="eButton" onclick="startTestTones()">Start Test Tone</button>
    </div>

    <script type="text/javascript">
      // Silent interval between the test tones.
      var testToneInterval = 0.1;

      // The safe range for the equal loudness of sinusoid is roughly between
      // 200 ~ 1000Hz, which is A3(57) ~ C6(84). In this test, the starting
      // pitch is 220Hz and the interval is the whole tone. (2 MIDI pitch) 
      // With 16 speakers, the last test tone will play the MIDI pitch of
      // F6(89), which is 1396Hz.
      var startMIDIPitch = 57;

      var eMaxChannelCount = document.querySelector('#eMaxChannelCount');
      var eButton = document.querySelector('#eButton');
      var eChannelIndex = document.querySelector('#eChannelIndex');

      var context = new AudioContext();
      var maxChannelCount = context.destination.maxChannelCount;

      // Sets the destination properties for multichannel access.
      context.destination.channelCount = maxChannelCount;
      context.destination.channelCountMode = 'explicit';
      context.destination.channelInterpretation = 'discrete';

      // The ChannelMerger for the individual channel access.
      var merger = context.createChannelMerger(maxChannelCount);
      merger.channelCountMode = 'explicit';
      merger.channelInterpretation = 'discrete';
      merger.connect(context.destination);

      eMaxChannelCount.textContent = maxChannelCount;

      // Convert the MIDI pitch to frequency.
      function midi2freq(midiPitch) {
        return 440 * Math.pow(2, (midiPitch - 69) / 12);
      }

      // Play a test tone for the specified amount of duration at the channel.
      function playTestToneAtChannel(channelIndex, gain, duration) {
        var osc = context.createOscillator();
        var amp = context.createGain();
        osc.connect(amp);
        amp.connect(merger, 0, channelIndex);

        osc.onended = function () {
          var nextChannelIndex = channelIndex + 1;
          if (nextChannelIndex < maxChannelCount)
            playTestToneAtChannel(nextChannelIndex, gain, duration);
          else
            endTestTone();
        };

        // The pitch for each speaker goes up as the channel index increases.
        // Note that the interval is 2, whole tone.
        osc.frequency.value = midi2freq(startMIDIPitch + channelIndex * 2);

        // The channel index starts from 1.
        eChannelIndex.textContent = 'Channel #' + (channelIndex + 1);
        
        var now = context.currentTime;
        var toneDuration = duration - testToneInterval;

        // Add fade in and out to avoid the click noise.
        amp.gain.setValueAtTime(0.0, now);
        amp.gain.linearRampToValueAtTime(gain, now + toneDuration * 0.1);
        amp.gain.setValueAtTime(gain, now + toneDuration * 0.9);
        amp.gain.linearRampToValueAtTime(0.0, now + toneDuration);

        osc.start(now);
        osc.stop(now + duration);
      }

      // When the button is clicked the button to produce the test sound,
      // the button is grayed out so one cannot press again until the tone is
      // over. (producing sounds multiple times in a short period time will
      // hurt the speaker).
      function startTestTones() {
        eButton.disabled = true;

        // Math.SQRT1_2(=0.707..) is -3dB. This is necessary because 1.0 
        // amplitude can cause overload/distortion on some speakers.
        playTestToneAtChannel(0, Math.SQRT1_2, 1.0);
      }

      // The button needs to be active back again when the test tone is over.
      // The index number in DIV should also be reset.
      function endTestTone() {
        eButton.disabled = false;
        eChannelIndex.textContent = 'NONE';
      }
    </script>
  </body>
</html>
